<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <meta content="text/html; charset=ISO-8859-1"
 http-equiv="content-type">
  <title>udp</title>
</head>
<body style="background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);"
 link="#0000ee" alink="#0000ee" vlink="#551a8b">
<h3>UDP stuff</h3>
<br>
<ul>
  <li>replicate images of selected objects to remote clients</li>
  <li>client and server may have different types mutually
interconnected, the client type knows how to get and apply updates from
the server side object<br>
  </li>
  <li>updates are sent as differences between known client state and
current server state</li>
</ul>
<br>
A<br>
<ul>
  <li>this can be a queue of updates made to server object, the
replicator marks the last acknowledged client state (update id) and
automatically sends the set of updates to client</li>
  <li>if the set becomes too large, or if it's a new connection,
replicator may send whole object instead of the updates</li>
  <li>the updates of visible objects (the objects someone sees) can be
stored in circular buffer</li>
  <li>disadvantage is that all operations on visible objects should log
the procedure</li>
  <li>this would mean that calls to the object should be logged
somehow, preferably automatically, and that the logging should be
switchable on and off, depending on whether someone sees (looks at) the
object or not</li>
  <li>logging can be done with a circular-buffer binstream</li>
  <li>another disadvantage is that the client side must have dispatcher
that knows what operation should be called, so it would be better if
the operation id was generated automatically</li>
  <li>if the operation was logged in a way like pushing all arguments
or data to binstream and flushing, we should somehow generate the id <br>
  </li>
</ul>
<span style="font-family: monospace;">&nbsp;&nbsp;&nbsp; void
upd_speed( const vec3&amp; dir, int flg )<br>
&nbsp;&nbsp;&nbsp; {<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; log_op(1) &lt;&lt; dir &lt;&lt;
flg;<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ...<br>
<br>
</span>
<ul>
  <li><span style="font-family: monospace;">log_op</span> is a
generated function that returns binstream object, but first writes the
operation id to it, along with the current server time<br>
  </li>
  <li>the best thing would be if we did not have to write the ordinals,
but this seems unavoidable</li>
  <li>generator would pick all occurrences of such constructs and
output
appropriate code for client</li>
  <li>it can (and should) also check for ambiguity in the ordinals <br>
  </li>
  <li>alternatively, the log operation may be specified in a specially
formatted comment before the method header</li>
  <li>generator would then output another class on server side, that
would list all public (logged) methods, and upon calling it will log
the arguments and call the original method</li>
  <li>this would force the server to access such objects through a
virtual interface, what may be undesirable</li>
  <li>theoretically, client dispatcher would pick the operation id and
remaining specific parameters from the stream, and then advance
simulation time of the client and call corresponding method</li>
</ul>
B<br>
<ul>
  <li>we may better log the results than the operands, as the method
may use some other resources to determine the final effect on object<br>
  </li>
  <li>then if there are more calls to the object, we would not create
log entries after each call, instead we'd rather transfer only the
changed stuff when
requested to</li>
  <li>alternatively, spin count variable(s) can be used,
that are incremented whenever an operation changes the object or parts
of
it</li>
  <li>the replicator would just compare the counts in the object and in
the view, and then sends the whole
object (or the touched independent parts of it)</li>
  <li>this would require methods modifying the object's state to
increment spin count variables of
parts they touch, and a custom method that receives 'dirty' flags for
parts of class that shall be sent as updates, and packs the
corresponding data into
binstream</li>
  <li>an unpack method on the client would read the flags
and subsequent data and update the client object accordingly</li>
  <li>note that with each update the server sends the current
simulation time; client
must first advance object's state to specified time, apply the update,
and
advance object's state to current client time<br>
  </li>
</ul>
C<br>
<ul>
  <li>another possibility is to store a copy of the last known state of
object for each view, and send differences between it and current
object's state</li>
  <li>this would require some sort of a compare operator, that receives
an old copy and goes through all relevant members, generating the
differential information on the fly</li>
  <li>this would also involve copying the current object for future
comparisons</li>
  <li>the question is if just detecting changes and sending whole
object wouldn't be better most of the time<br>
  </li>
</ul>
<br>
Seems that B would be the best choice.<br>
How can we automate the generator.<br>
&nbsp;&nbsp;&nbsp; If class members were kept in groups, constrained by
specially formated comments, the generator can take care of outputting
corresponding pack and unpack code automatically. The code can still be
overridden by an user-supplied packer and unpacker code, if the comment
said so. The format includes a group id, and the methods that change
members from specific group should use macro that
increments spin count for that group.<br>
&nbsp;&nbsp;&nbsp; Theoretically, members changed and even the best
group layout
can be computed automatically by analysis of the code, but this may
grow too complex and error prone. Programmer would still need to
call the macro that increments particular spin count variable.<br>
<br>
<span style="font-family: monospace;"></span>
<table style="text-align: left; width: 100%;" border="1" cellspacing="2"
 cellpadding="2">
  <tbody>
    <tr>
      <td style="vertical-align: top;"><span
 style="font-family: monospace;">#define CLUSTER(name)&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp; uint _spin_count_#name;&nbsp;&nbsp; &nbsp;
//define spin count variable<br>
#define DIRTY(name)&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;
++_spin_count_#name&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; //increment
the spin count variable<br>
      </span><br>
      <span style="font-family: monospace;"><span
 style="font-weight: bold;">struct </span>Moveable<br>
{<br>
&nbsp;&nbsp;&nbsp; <span style="font-weight: bold;">CLUSTER</span>(coord);<br>
&nbsp;&nbsp;&nbsp; int xyz[4];<br>
&nbsp;&nbsp;&nbsp; int dir[4];<br>
&nbsp;&nbsp;&nbsp; int speed[4];<br>
&nbsp;&nbsp;&nbsp; int accel[4];<br>
      <br>
&nbsp;&nbsp;&nbsp; <span style="font-weight: bold;">CLUSTER</span>(carry);<br>
&nbsp;&nbsp;&nbsp; dynarray&lt;WeaponO&gt; weapons;<br>
      <br>
&nbsp;&nbsp;&nbsp; //$cluster<br>
};<br>
      </span> <br>
      <span style="font-family: monospace;"><span
 style="font-weight: bold;">void </span>replicate_out_cluster_coord(
binstream&amp; out, const Moveable&amp; obj )</span><br
 style="font-family: monospace;">
      <span style="font-family: monospace;">{<br>
&nbsp;&nbsp;&nbsp; out.write_raw( obj.xyz, sizeof(Moveable::xyz) +
sizeof(</span><span style="font-family: monospace;"><span
 style="font-family: monospace;">Moveable::</span></span><span
 style="font-family: monospace;">dir) + sizeof(</span><span
 style="font-family: monospace;"><span style="font-family: monospace;">Moveable::</span></span><span
 style="font-family: monospace;">speed) + sizeof(</span><span
 style="font-family: monospace;"><span style="font-family: monospace;">Moveable::</span></span><span
 style="font-family: monospace;">accel) );<br
 style="font-family: monospace;">
      </span><span style="font-family: monospace;">}</span></td>
    </tr>
  </tbody>
</table>
<span style="font-family: monospace;"><br>
</span><span style="font-family: monospace;"><br>
</span>&nbsp;&nbsp;&nbsp; In addition to the state
replication/synchronization
mechanism, an event
replication mechanism can be used to log and transmit single calls.
This can be used
whenever the client ought to know that a particular event occurred,
without
an urgent need to know the changes that were caused by it (a
consequence of the
event on the object).<br>
&nbsp;&nbsp;&nbsp; Events are queued in an event buffer as one byte
values, totaling 255 events available. Optional arguments may be
passed into the buffer, in which case the generator should attempt to
generate packing and unpacking code.<br>
<br>
&nbsp;&nbsp;&nbsp; On the client side, events are dispatched as calls
to empty virtual methods that accord with the events. In order to
utilize them, programmer must derive the class and override requested
virtual methods (events). Eventually this can be one method that
receives event id and binstream with optional arguments, in what case
programmer should provide code for both argument packer and unpacker.<br>
<br>
&nbsp;&nbsp;&nbsp; Event queue is a preallocated fixed-size array, that
is created when a view is connected to the object and when first
event is posted. Events posted when no view is connected have no
effect. The event queue is a circular buffer, so the older events are
overwritten when there's no more space in the buffer. Theoretically, if
the buffer size is too small and/or events with optional arguments take
too much space, a client may miss some older events under problematic
connections, since clients are getting updates as differences from the
last
acknowledged state. This should not normally be a problem, since the
connection failures affect the replication synchronization anyway, but
it means that one cannot rely on the events for critical stuff. Normal
state replication would still correctly synchronize the states after a
failure.<span style="color: rgb(153, 0, 0); font-style: italic;"><br>
problem:
circular buffer should know about length of opt.arguments so it may
skip them when overwriting</span><br
 style="color: rgb(153, 0, 0); font-style: italic;">
<span style="color: rgb(153, 0, 0); font-style: italic;">these can be
computed by the generator when generated automatically, but it's a
problem if the code was provided</span><br
 style="color: rgb(153, 0, 0); font-style: italic;">
<span style="color: rgb(153, 0, 0); font-style: italic;">maybe we
should not allow optional arguments, or provided code ..<br>
[note] optional arguments may come handy if we do not have some values
passed to the event-producing code on the server reflected in the
object's replicated data, but they still might be useful on the client,
although not critically needed<br>
seems that automatically generated code would suffice<br>
we can use 2 bytes per event, one byte (or part) would contain size of
arguments<br>
</span><br>
A <span style="text-decoration: underline;">view</span> is an
interface object that manages replication of the state for object it is
bound to. It
keeps values of spin count variables for the latest replication
acknowledged by its remote client.<br>
A <span style="text-decoration: underline;">viewer</span> manages
several view interfaces for one remote client. It invokes the
replication,
collecting updates from all views and sending them to the client. It
also accepts updates from the client-controlled objects that lie within
allowed time frame.<br>
<br>
How it works:<br>
<ul>
  <li>client keeps last reference object received/patched from the
server</li>
  <li>client uses <span style="font-family: monospace;">advance()</span>
function on object to interpolate it's status to specified (forward)
time</li>
  <li>upon receiving a patch with specified event time, client patches
it's reference object at the event time and then advances it to current
client time</li>
  <li>client sends an acknowledge datagram along with the commands for
controlled objects periodically</li>
  <li>server keeps spin counts (view) for the last acknowledged state
and a (<span style="color: rgb(153, 0, 0);">limited?</span>) number of
views for states that went out since it</li>
  <li>after the server receives an acknowledgment for a new state, this
one becomes the reference state and anything older is discarded</li>
  <li>a bit field in the
state update header specifies which clusters are contained within
the update (particular bit set to one)</li>
  <li>client keeps only one copy of reference object, so if it
acknowledges a state, it expects server to send state updates from
specified state</li>
  <li>if server doesn't receive ack, it continues sending updates from
the last ack state</li>
  <li>client accepts updates that are based on an older reference state
normally</li>
  <li>if server receives ack for a state that is newer than the
reference state, but it isn't contained in the stack buffer (the buffer
has
overflown), it goes from the nearest older state in the buffer (which
usually is the reference one), and advances to and picks the newest
state that has the same bit field (so it doesn't introduce updates that
client might not have received)</li>
  <li>but since the server still must send an update, the new reference
state cannot be the most current one, so that next turn would send
something<br>
  </li>
  <li>this means client can accept updates that are based on a newer
state too, since the update is guaranteed not to miss anything (the
update completely overwrites previous updates)</li>
  <li>the client has to remember the reference state identifier it
uses, and
send ack with that id</li>
  <li>client rejects updates that have lower state id than its
reference state</li>
</ul>
<h4>Controlled objects, client commands</h4>
&nbsp;&nbsp;&nbsp; Server runs the simulation that steadily lags behind
the actual time <span style="font-family: monospace;">t</span><sub
 style="font-family: monospace;">a</sub> with the delay <span
 style="font-family: monospace;">T</span><sub
 style="font-family: monospace;">w</sub> which roughly equals to trip
delay from the slowest client plus a constant. This lagging time is
called a
simulation time <span style="font-family: monospace;">t</span><sub
 style="font-family: monospace;">s</sub>.<br>
<br>
&nbsp;&nbsp;&nbsp; A message from client stating that it
performed an action
at past time not earlier than the current simulation time will be
queued
for simulation. Messages about events that occurred earlier than the
simulation time are discarded, unless they are marked
as
floating events. A floating event is supposed to happen at the
specified time, but if the information about it arrives later than the
server's simulation time, the event still would be accepted, but as if
it
happened right at the time t<sub>s</sub> instead.<br>
<ul>
  <li>server accepts commands for objects controlled by clients</li>
  <li>these commands must lie within allowed time window, that means
maximally <span style="font-family: monospace;">T</span><sub
 style="font-family: monospace;">w</sub> before the current server time
    <span style="font-family: monospace;">t</span><sub
 style="font-family: monospace;">a</sub><br>
  </li>
  <li>older commands can be dropped completely or adjusted to
acceptable value, depending upon the command type</li>
  <li>commands are applied to reference state that was advanced to
command time</li>
  <li>since the commands may come anytime within the frame, randomly
ordered, it makes sense to apply them only after the time frame already
elapses and
no further commands can come</li>
  <li>this may also be the right time to send updates to clients<br>
  </li>
  <li>the server periodically processes collected commands, and
provides space for simulation of autonomous objects (uncontrolled)</li>
  <li>there's part that periodically evaluates all objects and computes
the
next state<br>
  </li>
</ul>
<span style="font-family: monospace;"><br>
</span><br>
Update log<br>
&nbsp;&nbsp;&nbsp; - combination of&nbsp; spin count variable and a
history log to be able to retrieve incremental updates<br>
<br>
<span style="font-family: monospace;">&nbsp;&nbsp;&nbsp;
dynarray&lt;type&gt; _data;<br>
<br>
</span><span style="font-family: monospace;"><span
 style="font-family: monospace;">&nbsp;&nbsp;&nbsp; uint
_spin;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; ///&lt; change detection<br>
&nbsp;&nbsp;&nbsp; circularstreambuf _bcb;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; ///&lt; update buffer<br>
</span></span><span style="font-family: monospace;"></span><br>
<span style="font-family: monospace;"></span><br>
<span style="font-family: monospace;"></span>
</body>
</html>
